package demo3;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;

/**
 * 模拟人需要依赖衣服才可以出门
 *
 * 在Spring的编程模式中，我们的核心思想为"控制反转"
 * 指的是：当我们做某事需要一个类时，原本需要我们自行实例化，并进行
 *        初始化来控制。现在这个动作转交给Spring容器来完成
 *        这种通过编码控制一个对象实例化，初始化的事情反转交给
 *        Spring来完成，被称为"控制反转"
 *
 * Spring的控制反转，将我们依赖的对象进行管理
 * 什么是"依赖"(dependency)
 * 当我们完成某项工作需要用到另一个类，那么这个类就是我们的"依赖"。
 *
 * 我们需要获取依赖对象时，在Spring中可以采取两种方式:
 * 1:主动要:context.getBean(....)
 * 2:可以在当前类上声明一个属性，并使用相应的注解告诉Spring我需要用到某个依赖的对象，那么
 *   Spring在创建当前对象时会自然的将需要依赖的对象通过属性赋值"注入"到当前对象上供我们使用
 *   这个过程称为"依赖注入"
 *   第二种是我们后期最常用的方式
 *
 * 本案例来演示依赖注入
 * 环境:
 * 1:当前类Person应当是一个被Spring管理的类
 * 2:当Person需要用到一个类(Shirt)时，可以在Person类上声明一个属性private Shirt shirt;
 * 3:由于我们希望让Spring将Shirt"注入到当前属性上",就是为当前属性赋值，此时该属性上需要
 *   添加注解:@Autowired（自动装配）
 * 4:如果一旦Spring容器创建Person时，就会发现其内部的属性shirt,并通过@Autowired得知该属性
 *   需要配Spring容器装配，那么如果Shirt也是受Spring容器管理的，那么就会自动设置上，完成
 *   Person需要依赖Shirt的情况。
 *
 */
@Component//首先如果需要利用依赖注入，则当前类也要受Spring容器管理
public class Person {
//    @Autowired//自动装配，那么Spring容器创建Person时就会将Shirt注入进来(Shirt也要受Spring容器管理)
//    private Shirt shirt;//Person需要依赖Shirt，就将其定义为一个属性

//    @Autowired
//    private Sweater sweater;

    /*
        面向接口的编程思想
        依赖在这里定义为属性时，都建议定义为成接口或超类，而不是具体的某一种子类型。
        这样将来业务改变而导致需要更换依赖的类时，这里无需进行任何修改

        类似与:
        人不应当依赖具体的某一种衣服类型，比如毛衣，T恤等。而是依赖"衣服"这个超类。
        这样无论将来提供的是何种衣服，这里都不需要进行修改。
     */
    @Autowired
    /*
        当我们使用@Autowired注解需要Spring容器进行自动装配时，如果这里接口类型有多个实现类
        都被Spring容器管理时，那么容器在装配时就会报错，因为无法选择使用哪一个实现类。
        例如本案例:
        这里定义依赖的类型是Clothes.
        而Spring容器内部同时管理了Shirt,Sweater(它们都实现了Clothes接口)，此时
        就会出现异常。

        解决办法:
        1:保证Spring容器内部Clothes的实现类只有一个被管理
        2:可以为每一个被管理的类添加一个"组件名"-在使用@Component注解时用参数定义
          例如:
          @Component("winter")
          public class Sweater implements Clothes {...}

          @Component("summer")
          public class Shirt implements Clothes {...}

          那么在Person这里定义依赖时，除了添加@Autowired注解之外，再添加一个
          注解:@Qualifier，并指名需要将哪个组件注入进来，这里参数指定组件名即可
          例如:
          如果需要使用Shirt，则这里@Qualifier("summer")就可以了，因为Shirt
          类上@Component("summer")

     */
    @Qualifier("summer")
    private Clothes clothes;

    public void play(){

        Object obj = new Person();

        //穿上衣服，出去玩
        /*
            1:最原始方式，自己创建
            Shirt shirt = new Shirt()

            2:主动向Spring容器要
            Shirt shrit = context.getBean(Shirt.class);
            但是上述模式，至少要先有Spring容器，所以如果每次都创建容器会比较麻烦

            3:依赖注入
            将Shirt shirt定义为一个属性，让Spring容器在创建Person的同时将Shirt注入进来
         */
//        shirt.wear();
//        sweater.wear();
        clothes.wear();
        System.out.println("出去玩!");
    }
}










